#include <jni.h>
#include <android/log.h>
#include <stdexcept>
#include <string>

using namespace std;

#include "SoundTouch.h"
#include "WavFile.h"

#define LOGV(...)   __android_log_print((int)ANDROID_LOG_INFO, "SOUNDTOUCH", __VA_ARGS__)

static string _errMsg = "";

#define BUFF_SIZE 4096


using namespace soundtouch;


// Set error message to return
static void _setErrmsg(const char *msg) {
    _errMsg = msg;
}


#ifdef _OPENMP

#include <pthread.h>
extern pthread_key_t gomp_tls_key;
static void * _p_gomp_tls = NULL;

/// Function to initialize threading for OpenMP.
///
/// This is a workaround for bug in Android NDK v10 regarding OpenMP: OpenMP works only if
/// called from the Android App main thread because in the main thread the gomp_tls storage is
/// properly set, however, Android does not properly initialize gomp_tls storage for other threads.
/// Thus if OpenMP routines are invoked from some other thread than the main thread,
/// the OpenMP routine will crash the application due to NULL pointer access on uninitialized storage.
///
/// This workaround stores the gomp_tls storage from main thread, and copies to other threads.
/// In order this to work, the Application main thread needws to call at least "getVersionString"
/// routine.
static int _init_threading(bool warn)
{
    void *ptr = pthread_getspecific(gomp_tls_key);
    LOGV("JNI thread-specific TLS storage %ld", (long)ptr);
    if (ptr == NULL)
    {
        LOGV("JNI set missing TLS storage to %ld", (long)_p_gomp_tls);
        pthread_setspecific(gomp_tls_key, _p_gomp_tls);
    }
    else
    {
        LOGV("JNI store this TLS storage");
        _p_gomp_tls = ptr;
    }
    // Where critical, show warning if storage still not properly initialized
    if ((warn) && (_p_gomp_tls == NULL))
    {
        _setErrmsg("Error - OpenMP threading not properly initialized: Call SoundTouch.getVersionString() from the App main thread!");
        return -1;
    }
    return 0;
}

#else

static int _init_threading(bool warn) {
    // do nothing if not OpenMP build
    return 0;
}

#endif


// Processes the sound file
static int _processFile(SoundTouch *pSoundTouch, const char *inFileName, const char *outFileName) {
    int nSamples;
    int nChannels;
    int buffSizeSamples;
    SAMPLETYPE sampleBuffer[BUFF_SIZE];

    // open input file
    WavInFile inFile(inFileName);
    int sampleRate = inFile.getSampleRate();
    int bits = inFile.getNumBits();
    nChannels = inFile.getNumChannels();

    // create output file
    WavOutFile outFile(outFileName, sampleRate, bits, nChannels);
    //采样
    pSoundTouch->setSampleRate(sampleRate);//设置采样率
    pSoundTouch->setChannels(nChannels);//设置声道，1 = mono单声道, 2 = stereo立体声

    // 不能用断言，不然抛出的异常，Java捕获不到
    // assert(nChannels > 0);
    // 通道数，必须大于0
    if (nChannels <= 0) {
        return -1;
    }

    buffSizeSamples = BUFF_SIZE / nChannels;

    // Process samples read from the input file
    while (inFile.eof() == 0) {
        int num;

        // Read a chunk of samples from the input file
        num = inFile.read(sampleBuffer, BUFF_SIZE);
        nSamples = num / nChannels;

        // Feed the samples into SoundTouch processor
        pSoundTouch->putSamples(sampleBuffer, nSamples);

        // Read ready samples from SoundTouch processor & write them output file.
        // NOTES:
        // - 'receiveSamples' doesn't necessarily return any samples at all
        //   during some rounds!
        // - On the other hand, during some round 'receiveSamples' may have more
        //   ready samples than would fit into 'sampleBuffer', and for this reason
        //   the 'receiveSamples' call is iterated for as many times as it
        //   outputs samples.
        do {
            nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
            outFile.write(sampleBuffer, nSamples * nChannels);
        } while (nSamples != 0);
    }

    // Now the input file is processed, yet 'flush' few last samples that are
    // hiding in the SoundTouch's internal processing pipeline.
    pSoundTouch->flush();
    do {
        nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
        outFile.write(sampleBuffer, nSamples * nChannels);
    } while (nSamples != 0);

    // 成功，一定要返回0，如果不返回，C++编译不会报错，但是运行时肯定会抛异常
    return 0;
}



extern "C"
JNIEXPORT jstring JNICALL
Java_com_sundy_soundtouch_SoundTouch_getVersionString(JNIEnv *env, jclass clazz) {
    const char *verstr;
    LOGV("JNI call SoundTouch.getVersionString");
    //Call example SoundTouch routine
    verstr = SoundTouch::getVersionString();
    /// gomp_tls storage bug workaround - see comments in _init_threading() function!
    _init_threading(false);
    int threads = 0;
    #pragma omp parallel
    {
    #pragma omp atomic
        threads++;
    };
    LOGV("JNI thread count %d", threads);
    return env->NewStringUTF(verstr);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_setTempo(JNIEnv *env, jobject thiz, jlong handle,
                                              jfloat tempo) {
    SoundTouch *ptr=(SoundTouch *)handle;
    ptr->setTempo(tempo);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_setPitchSemiTones(JNIEnv *env, jobject thiz, jlong handle,
                                                       jfloat pitch) {
   SoundTouch *ptr=(SoundTouch *)handle;
   ptr->setPitchSemiTones(pitch);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_setSpeed(JNIEnv *env, jobject thiz, jlong handle,
                                              jfloat speed) {
    SoundTouch *ptr=(SoundTouch *)handle;
    ptr->setRate(speed);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_sundy_soundtouch_SoundTouch_processFile(JNIEnv *env, jobject thiz, jlong handle,
                                                 jstring input_file, jstring output_file) {
    SoundTouch *ptr=(SoundTouch *)handle;
    const char *inputFile=env->GetStringUTFChars(input_file,0);
    const char *outputFile=env->GetStringUTFChars(output_file,0);

    LOGV("JNI process file %s", inputFile);

    /// gomp_tls storage bug workaround - see comments in _init_threading() function!
    if(_init_threading(true)){
        return -1;
    }
    try {
        _processFile(ptr,inputFile,outputFile);
    }catch (const runtime_error &e){
        const char *err=e.what();
        // An exception occurred during processing, return the error message
        LOGV("JNI exception in SoundTouch::processFile: %s", err);
        _setErrmsg(err);
        return -1;
    }
    env->ReleaseStringUTFChars(input_file,inputFile);
    env->ReleaseStringUTFChars(output_file,outputFile);
    return 0;

}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_sundy_soundtouch_SoundTouch_getErrorString(JNIEnv *env, jclass clazz) {
 jstring result=env->NewStringUTF(_errMsg.c_str());
  _errMsg.clear();
    return result;
}

extern "C"
JNIEXPORT jlong JNICALL
Java_com_sundy_soundtouch_SoundTouch_newInstance(JNIEnv *env, jclass clazz) {

    return (jlong)(new SoundTouch());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_deleteInstance(JNIEnv *env, jobject thiz, jlong handle) {
    SoundTouch *ptr=(SoundTouch *)handle;
    delete ptr;
}extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_setPitch(JNIEnv *env, jobject thiz, jlong handle,
                                              jfloat pitch) {
    SoundTouch *ptr=(SoundTouch *)handle;
     ptr->setPitch(pitch);
}extern "C"
JNIEXPORT void JNICALL
Java_com_sundy_soundtouch_SoundTouch_setPitchOctaves(JNIEnv *env, jobject thiz, jlong handle,
                                                     jfloat pitch) {
    SoundTouch *ptr=(SoundTouch *)handle;
    ptr->setPitchOctaves(pitch);
}